/*
 *    Copyright (c) 2018-2025, lengleng All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: lengleng (wangiegie@gmail.com)
 */
package com.pig4cloud.pigx.contract.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.i18n.util.MessageUtils;
import com.pig4cloud.pigx.common.customize.mysql.utils.MysqlUtil;
import com.pig4cloud.pigx.common.customize.security.utils.SecurityUtil;
import com.pig4cloud.pigx.common.customize.utils.CommonUtil;
import com.pig4cloud.pigx.common.seata.utils.SeataUtils;
import com.pig4cloud.pigx.common.security.service.PigxUser;
import com.pig4cloud.pigx.contract.bo.ContractExistPositionInfoBO;
import com.pig4cloud.pigx.contract.constant.I18nMessages;
import com.pig4cloud.pigx.contract.dto.AppContractMarginChangeDTO;
import com.pig4cloud.pigx.contract.dto.AppContractMultipleChangeDTO;
import com.pig4cloud.pigx.contract.entity.ContractPosition;
import com.pig4cloud.pigx.contract.entity.ContractPositionMarginDetail;
import com.pig4cloud.pigx.contract.enums.ContractOrderEnum;
import com.pig4cloud.pigx.contract.enums.ContractPositionEnum;
import com.pig4cloud.pigx.contract.enums.ContractPositionMarginDetailEnum;
import com.pig4cloud.pigx.contract.executer.order.supporter.OrderCalculator;
import com.pig4cloud.pigx.contract.executer.position.PositionExecutor;
import com.pig4cloud.pigx.contract.executer.position.supporter.PositionCalculator;
import com.pig4cloud.pigx.contract.mapper.ContractPositionMapper;
import com.pig4cloud.pigx.contract.mongo.entity.ContractSymbolMongo;
import com.pig4cloud.pigx.contract.service.ContractPositionService;
import com.pig4cloud.pigx.contract.utils.ContractUtils;
import com.pig4cloud.pigx.contract.vo.ContractRankingVO;
import com.pig4cloud.pigx.wallet.dto.WalletContractAccountChangeDTO;
import com.pig4cloud.pigx.wallet.enums.WalletContractAccountDetailEnum;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * 仓位表
 *
 * @since 2022-08-11 10:08:43
 */
@Service
@AllArgsConstructor
public class ContractPositionServiceImpl extends ServiceImpl<ContractPositionMapper, ContractPosition> implements ContractPositionService {

    private final ContractUtils contractUtils;
    private final CommonUtil commonUtil;
    private final MysqlUtil mysqlUtil;
    private final PositionExecutor positionExecutor;

    /**
     * 多宝比计算
     *
     * @return
     */
    @Override
    public R<?> getProportion() {
        // 当前持仓 做多/做空 数量
        QueryWrapper<ContractPosition> wrapper = new QueryWrapper<>();
        wrapper.select("count( 1 ) 'count',side");
        wrapper.eq("status", ContractPositionEnum.Status.OPEN.getValue());
        wrapper.groupBy("side");
        List<ContractPosition> positionList = baseMapper.selectList(wrapper);
        // 如果出现只有做空/做多的情况，此时比例设置为0
        if (positionList.size() < 2) {
            return R.ok(0);
        }
        int many = 0;
        int empty = 0;
        for (ContractPosition e : positionList) {
            if (ContractOrderEnum.Side.MORE.getValue().equals(e.getSide())) {
                many = e.getCount();
            }
            if (ContractOrderEnum.Side.EMPTY.getValue().equals(e.getSide())) {
                empty = e.getCount();
            }
        }
        return R.ok(BigDecimal.valueOf((float) many / empty).setScale(2, RoundingMode.HALF_UP));
    }

    @Override
    public IPage<ContractRankingVO> getProfitRanking(Page page) {
        return baseMapper.getProfitRanking(page);
    }

    @Override
    public IPage<ContractRankingVO> getLossRanking(Page page) {
        return baseMapper.getLossRanking(page);
    }

    /**
     * 修改杠杆倍数
     *
     * @param dto dto
     * @since 2022/9/2 14:58
     */
    @Override
    public void multipleChange(AppContractMultipleChangeDTO dto) {
        PigxUser user = SecurityUtil.getLoginUserNotNull();
        dto.setUserId(user.getId());
        positionExecutor.multipleChange(dto);
    }

    /**
     * 逐仓调整保证金
     *
     * @param dto dto
     * @since 2022/9/6 22:02
     */
    @Override
    public void marginChange(AppContractMarginChangeDTO dto) {
        if (BigDecimal.ZERO.compareTo(dto.getAmount()) >= 0) {
            throw new RuntimeException("数据异常，amount不能小于等于0");
        }

        try {
            PigxUser user = SecurityUtil.getLoginUserNotNull();

            // 查询仓位信息，并锁住
            ContractPosition condition = new ContractPosition();
            condition.setId(dto.getPositionId());
            // 确保是本人仓位
            condition.setUserId(user.getId());

            // 开启本地事务
            mysqlUtil.startLocalTx();
            ContractPosition position = contractUtils.getOneCurrentPosition(condition, true);
            ContractUtils.checkCurrentPosition(position, false);

            if (Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), position.getNeedLiquidate())) {
                // 仓位强平中
                throw new RuntimeException(MessageUtils.get(I18nMessages.POSITION_IS_STRONG_IN_FLAT));
            }

            // 判断是否是逐仓
            if (!Objects.equals(ContractPositionEnum.Mode.ISOLATED.getValue(), position.getMode())) {
                // 异常操作
                throw new RuntimeException(MessageUtils.get(I18nMessages.ABNORMAL_OPERATION));
            }

            // 查询币对信息
            ContractSymbolMongo symbol = contractUtils.getSymbolById(position.getSymbolId());
            ContractUtils.checkSymbol(symbol);

            // 设置精度
            dto.setAmount(CommonUtil.setUSDTScale(dto.getAmount()));
            // JVM根据计算类型修改仓位保证金
            if (Objects.equals(CommonDictEnum.CalculationType.ADD.getValue(), dto.getCalculationType())) {
                // 增加
                position.setMargin(position.getMargin().add(dto.getAmount()));
            } else {
                // 减少
                position.setMargin(position.getMargin().subtract(dto.getAmount()));

                // 计算标准保证金
                BigDecimal positionValue = OrderCalculator.getOrderValue(position.getOpenAvgPrice(), position.getNowNum());
                BigDecimal standardMargin = PositionCalculator.getStandardMargin(positionValue, position.getMultiple());
                if (position.getMargin().compareTo(standardMargin) < 0) {
                    // 超过可操作仓位保证金上限
                    throw new RuntimeException(MessageUtils.get(I18nMessages.EXCEED_THE_LIMIT_OF_OPERABLE_POSITION_MARGIN));
                }
            }

            // JVM重新计算强平价和是否需要强平
            PositionCalculator.calculateLiquidateIsolated(position, symbol);
            // 判断是否需要强平
            if (Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), position.getNeedLiquidate())) {
                // 将导致仓位强平，请修改调整金额
                throw new RuntimeException(MessageUtils.get(I18nMessages.POSITION_WILL_BE_STRONGLY_CLOSED));
            }

            // 根据加/减获得操作类型
            WalletContractAccountDetailEnum.OperationType operationType = Objects.equals(CommonDictEnum.CalculationType.ADD.getValue(), dto.getCalculationType()) ?
                    WalletContractAccountDetailEnum.OperationType.ADD
                    : WalletContractAccountDetailEnum.OperationType.MINUS;

            // 开启全局事务
            SeataUtils.startGlobalTx();

            // 修改钱包余额
            WalletContractAccountChangeDTO param = new WalletContractAccountChangeDTO(user.getId(),
                    dto.getAmount(),
                    null,
                    position.getSymbolId(),
                    position.getSymbolName(),
                    WalletContractAccountDetailEnum.BusinessType.POSITION_MARGIN,
                    operationType,
                    false);
            R<?> r = contractUtils.changeAsset(Collections.singletonList(param));
            if (!r.getIsSuccess()) {
                throw new RuntimeException(r.getMsg());
            }

            // 修改仓位信息
            if (baseMapper.updateById(position) != 1) {
                throw new RuntimeException(MessageUtils.get(I18nMessages.FAILED_TO_MODIFY_POSITION_INFORMATION));
            }

            // 提交全局事务
            SeataUtils.commitGlobalTx();
            // 提交本地事务
            mysqlUtil.commitLocalTx();

            // 发送重算全仓强平价消息
            contractUtils.sendPositionCrossCalculate(position.getUserId());

            // 发送保证金明细消息
            ContractPositionMarginDetail detail = new ContractPositionMarginDetail();
            detail.setPositionId(position.getId());
            detail.setMargin(dto.getAmount());
            detail.setBusinessType(ContractPositionMarginDetailEnum.BusinessType.CHANGE_MARGIN.getValue());
            // 根据保证金操作类型获取保证金明细操作类型
            ContractPositionMarginDetailEnum.OperationType marginOperationType = Objects.equals(CommonDictEnum.CalculationType.ADD.getValue(), dto.getCalculationType()) ?
                    ContractPositionMarginDetailEnum.OperationType.ADD
                    : ContractPositionMarginDetailEnum.OperationType.MINUS;
            detail.setOperationType(marginOperationType.getValue());
            detail.setLiquidatePrice(position.getLiquidatePrice());
            contractUtils.sendSavePositionMarginDetailMq(Collections.singletonList(detail));
        } catch (Throwable e) {
            // 回滚全局事务
            SeataUtils.rollbackGlobalTx();
            // 回滚本地事务
            mysqlUtil.rollbackLocalTx();
            throw e;
        }
    }

    /**
     * 查询已有仓位信息
     *
     * @param symbolId 币对id
     * @return {@link ContractExistPositionInfoBO}
     * @since 2022/9/30 10:27
     */
    @Override
    public ContractExistPositionInfoBO existInfo(Long symbolId) {
        PigxUser user = SecurityUtil.getLoginUserNotNull();
        return contractUtils.getExistPositionInfo(user.getId(), symbolId);
    }
}
